home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 3504 / 3504.xpi / components / zotero-protocol-handler.js < prev    next >
Text File  |  2008-11-16  |  27KB  |  927 lines

  1. /*
  2.     ***** BEGIN LICENSE BLOCK *****
  3.     
  4.     Copyright (c) 2006  Center for History and New Media
  5.                         George Mason University, Fairfax, Virginia, USA
  6.                         http://chnm.gmu.edu
  7.     
  8.     Licensed under the Educational Community License, Version 1.0 (the "License");
  9.     you may not use this file except in compliance with the License.
  10.     You may obtain a copy of the License at
  11.     
  12.     http://www.opensource.org/licenses/ecl1.php
  13.     
  14.     Unless required by applicable law or agreed to in writing, software
  15.     distributed under the License is distributed on an "AS IS" BASIS,
  16.     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.     See the License for the specific language governing permissions and
  18.     limitations under the License.
  19.     
  20.     
  21.     Based on nsChromeExtensionHandler example code by Ed Anuff at
  22.     http://kb.mozillazine.org/Dev_:_Extending_the_Chrome_Protocol
  23.     
  24.     
  25.     ***** END LICENSE BLOCK *****
  26. */
  27.  
  28.  
  29. const ZOTERO_SCHEME = "zotero";
  30. const ZOTERO_PROTOCOL_CID = Components.ID("{9BC3D762-9038-486A-9D70-C997AF848A7C}");
  31. const ZOTERO_PROTOCOL_CONTRACTID = "@mozilla.org/network/protocol;1?name=" + ZOTERO_SCHEME;
  32. const ZOTERO_PROTOCOL_NAME = "Zotero Chrome Extension Protocol";
  33.  
  34. // Dummy chrome URL used to obtain a valid chrome channel
  35. // This one was chosen at random and should be able to be substituted
  36. // for any other well known chrome URL in the browser installation
  37. const DUMMY_CHROME_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
  38.  
  39.  
  40. function ChromeExtensionHandler() {
  41.     this.wrappedJSObject = this;
  42.     this._systemPrincipal = null;
  43.     this._extensions = {};
  44.     
  45.     
  46.     /*
  47.      * Report generation extension for Zotero protocol
  48.      *
  49.      * Example URLs:
  50.      *
  51.      * zotero://report/    -- library
  52.      * zotero://report/collection/12345
  53.      * zotero://report/search/12345
  54.      * zotero://report/items/12345-23456-34567
  55.      * zotero://report/item/12345
  56.      *
  57.      * Optional format can be specified after ids
  58.      *
  59.      *  - 'html', 'rtf', 'csv'
  60.      *  - defaults to 'html' if not specified
  61.      *
  62.      * e.g. zotero://report/collection/12345/rtf
  63.      * 
  64.      *
  65.      * Sorting:
  66.      *
  67.      *     - 'sort' query string variable
  68.      *  - format is field[/order] [, field[/order], ...]
  69.      *  - order can be 'asc', 'a', 'desc' or 'd'; defaults to ascending order
  70.      *
  71.      *  zotero://report/collection/13245?sort=itemType/d,title
  72.      */
  73.     var ReportExtension = new function(){
  74.         this.newChannel = newChannel;
  75.         
  76.         this.__defineGetter__('loadAsChrome', function () { return false; });
  77.         
  78.         function newChannel(uri){
  79.             var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  80.                 .getService(Components.interfaces.nsIIOService);
  81.             
  82.             var Zotero = Components.classes["@zotero.org/Zotero;1"]
  83.                 .getService(Components.interfaces.nsISupports)
  84.                 .wrappedJSObject;
  85.             
  86.             generateContent:try {
  87.                 var mimeType, content = '';
  88.                 
  89.                 var [path, queryString] = uri.path.substr(1).split('?');
  90.                 var [type, ids, format] = path.split('/');
  91.                 
  92.                 // Get query string variables
  93.                 if (queryString) {
  94.                     var queryVars = queryString.split('&');
  95.                     for (var i in queryVars) {
  96.                         var [key, val] = queryVars[i].split('=');
  97.                         switch (key) {
  98.                             case 'sort':
  99.                                 var sortBy = val;
  100.                                 break;
  101.                         }
  102.                     }
  103.                 }
  104.                 
  105.                 switch (type){
  106.                     case 'collection':
  107.                         var col = Zotero.Collections.get(ids);
  108.                         var results = col.getChildItems();
  109.                         break;
  110.                     
  111.                     case 'search':
  112.                         var s = new Zotero.Search(ids);
  113.                         var ids = s.search();
  114.                         break;
  115.                     
  116.                     case 'items':
  117.                     case 'item':
  118.                         var ids = ids.split('-');
  119.                         break;
  120.                         
  121.                     default:
  122.                         // Proxy CSS files
  123.                         if (type.match(/^detail.*\.css$/)) {
  124.                             var chromeURL = 'chrome://zotero/skin/report/' + type;
  125.                             var ios = Components.classes["@mozilla.org/network/io-service;1"]
  126.                                         .getService(Components.interfaces.nsIIOService);
  127.                             var uri = ios.newURI(chromeURL, null, null);
  128.                             var chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  129.                                         .getService(Components.interfaces.nsIChromeRegistry);
  130.                             var fileURI = chromeReg.convertChromeURL(uri);
  131.                             var ph = Components.classes["@mozilla.org/network/protocol;1?name=file"]
  132.                                         .createInstance(Components.interfaces.nsIFileProtocolHandler);
  133.                             var channel = ioService.newChannelFromURI(fileURI);
  134.                             return channel;
  135.                         }
  136.                         
  137.                         // Display all items
  138.                         var type = 'library';
  139.                         var s = new Zotero.Search();
  140.                         s.addCondition('noChildren', 'true');
  141.                         var ids = s.search();
  142.                 }
  143.                 
  144.                 if (!results) {
  145.                     var results = Zotero.Items.get(ids);
  146.                     
  147.                     if (!results) {
  148.                         mimeType = 'text/html';
  149.                         content = 'Invalid ID';
  150.                         break generateContent;
  151.                     }
  152.                 }
  153.                 
  154.                 var items = [];
  155.                 var itemsHash = {}; // key = itemID, val = position in |items|
  156.                 var searchItemIDs = {}; // hash of all selected items
  157.                 var searchParentIDs = {}; // hash of parents of selected child items
  158.                 var searchChildIDs = {}; // hash of selected chlid items
  159.                 
  160.                 var includeAllChildItems = Zotero.Prefs.get('report.includeAllChildItems');
  161.                 var combineChildItems = Zotero.Prefs.get('report.combineChildItems');
  162.                 
  163.                 var unhandledParents = {};
  164.                 for (var i=0; i<results.length; i++) {
  165.                     // Don't add child items directly
  166.                     // (instead mark their parents for inclusion below)
  167.                     var sourceItemID = results[i].getSource();
  168.                     if (sourceItemID) {
  169.                         searchParentIDs[sourceItemID] = true;
  170.                         searchChildIDs[results[i].getID()] = true;
  171.                         
  172.                         // Don't include all child items if any child
  173.                         // items were selected
  174.                         includeAllChildItems = false;
  175.                     }
  176.                     // If combining children or standalone note/attachment, add matching parents
  177.                     else if (combineChildItems || !results[i].isRegularItem()
  178.                             || results[i].numChildren() == 0) {
  179.                         itemsHash[results[i].getID()] = [items.length];
  180.                         items.push(results[i].toArray(2));
  181.                         // Flag item as a search match
  182.                         items[items.length - 1].reportSearchMatch = true;
  183.                     }
  184.                     else {
  185.                         unhandledParents[i] = true;
  186.                     }
  187.                     searchItemIDs[results[i].getID()] = true;
  188.                 }
  189.                 
  190.                 // If including all child items, add children of all matched
  191.                 // parents to the child array
  192.                 if (includeAllChildItems) {
  193.                     for (var id in searchItemIDs) {
  194.                         if (!searchChildIDs[id]) {
  195.                             var children = [];
  196.                             var item = Zotero.Items.get(id);
  197.                             if (!item.isRegularItem()) {
  198.                                 continue;
  199.                             }
  200.                             var func = function (ids) {
  201.                                 if (ids) {
  202.                                     for (var i=0; i<ids.length; i++) {
  203.                                         searchChildIDs[ids[i]] = true;
  204.                                     }
  205.                                 }
  206.                             };
  207.                             func(item.getNotes());
  208.                             func(item.getAttachments());
  209.                         }
  210.                     }
  211.                 }
  212.                 // If not including all children, add matching parents,
  213.                 // in case they don't have any matching children below
  214.                 else {
  215.                     for (var i in unhandledParents) {
  216.                         itemsHash[results[i].id] = [items.length];
  217.                         items.push(results[i].toArray(2));
  218.                         // Flag item as a search match
  219.                         items[items.length - 1].reportSearchMatch = true;
  220.                     }
  221.                 }
  222.                 
  223.                 if (combineChildItems) {
  224.                     // Add parents of matches if parents aren't matches themselves
  225.                     for (var id in searchParentIDs) {
  226.                         if (!searchItemIDs[id] && !itemsHash[id]) {
  227.                             var item = Zotero.Items.get(id);
  228.                             itemsHash[id] = items.length;
  229.                             items.push(item.toArray(2));
  230.                         }
  231.                     }
  232.                     
  233.                     // Add children to reportChildren property of parents
  234.                     for (var id in searchChildIDs) {
  235.                         var item = Zotero.Items.get(id);
  236.                         var parentItemID = item.getSource();
  237.                         if (!items[itemsHash[parentItemID]].reportChildren) {
  238.                             items[itemsHash[parentItemID]].reportChildren = {
  239.                                 notes: [],
  240.                                 attachments: []
  241.                             };
  242.                         }
  243.                         if (item.isNote()) {
  244.                             items[itemsHash[parentItemID]].reportChildren.notes.push(item.toArray());
  245.                         }
  246.                         if (item.isAttachment()) {
  247.                             items[itemsHash[parentItemID]].reportChildren.attachments.push(item.toArray());
  248.                         }
  249.                     }
  250.                 }
  251.                 // If not combining children, add a parent/child pair
  252.                 // for each matching child
  253.                 else {
  254.                     for (var id in searchChildIDs) {
  255.                         var item = Zotero.Items.get(id);
  256.                         var parentID = item.getSource();
  257.                         var parentItem = Zotero.Items.get(parentID);
  258.                         
  259.                         if (!itemsHash[parentID]) {
  260.                             // If parent is a search match and not yet added,
  261.                             // add on its own
  262.                             if (searchItemIDs[parentID]) {
  263.                                 itemsHash[parentID] = [items.length];
  264.                                 items.push(parentItem.toArray(2));
  265.                                 items[items.length - 1].reportSearchMatch = true;
  266.                             }
  267.                             else {
  268.                                 itemsHash[parentID] = [];
  269.                             }
  270.                         }
  271.                         
  272.                         // Now add parent and child
  273.                         itemsHash[parentID].push(items.length);
  274.                         items.push(parentItem.toArray(2));
  275.                         if (item.isNote()) {
  276.                             items[items.length - 1].reportChildren = {
  277.                                 notes: [item.toArray()],
  278.                                 attachments: []
  279.                             };
  280.                         }
  281.                         else if (item.isAttachment()) {
  282.                             items[items.length - 1].reportChildren = {
  283.                                 notes: [],
  284.                                 attachments: [item.toArray()]
  285.                             };
  286.                         }
  287.                     }
  288.                 }
  289.                 
  290.                 
  291.                 // Sort items
  292.                 if (!sortBy) {
  293.                     sortBy = 'title';
  294.                 }
  295.                 
  296.                 var sorts = sortBy.split(',');
  297.                 for (var i=0; i<sorts.length; i++) {
  298.                     var [field, order] = sorts[i].split('/');
  299.                     // Year field is really date field
  300.                     if (field == 'year') {
  301.                         field = 'date';
  302.                     }
  303.                     switch (order) {
  304.                         case 'd':
  305.                         case 'desc':
  306.                             order = -1;
  307.                             break;
  308.                         
  309.                         default:
  310.                             order = 1;
  311.                     }
  312.                     
  313.                     sorts[i] = {
  314.                         field: field,
  315.                         order: order
  316.                     };
  317.                 }
  318.                 
  319.                 
  320.                 var collation = Zotero.getLocaleCollation();
  321.                 var compareFunction = function(a, b) {
  322.                     var index = 0;
  323.                     
  324.                     // Multidimensional sort
  325.                     do {
  326.                         // In combineChildItems, use note or attachment as item
  327.                         if (!combineChildItems) {
  328.                             if (a.reportChildren) {
  329.                                 if (a.reportChildren.notes.length) {
  330.                                     a = a.reportChildren.notes[0];
  331.                                 }
  332.                                 else {
  333.                                     a = a.reportChildren.attachments[0];
  334.                                 }
  335.                             }
  336.                             
  337.                             if (b.reportChildren) {
  338.                                 if (b.reportChildren.notes.length) {
  339.                                     b = b.reportChildren.notes[0];
  340.                                 }
  341.                                 else {
  342.                                     b = b.reportChildren.attachments[0];
  343.                                 }
  344.                             }
  345.                         }
  346.                         
  347.                         var valA, valB;
  348.                         
  349.                         if (sorts[index].field == 'title') {
  350.                             // For notes, use content for 'title'
  351.                             if (a.itemType == 'note') {
  352.                                 valA = a.note;
  353.                             }
  354.                             else {
  355.                                 valA = a.title; 
  356.                             }
  357.                             
  358.                             if (b.itemType == 'note') {
  359.                                 valB = b.note;
  360.                             }
  361.                             else {
  362.                                 valB = b.title; 
  363.                             }
  364.                             
  365.                             valA = Zotero.Items.getSortTitle(valA);
  366.                             valB = Zotero.Items.getSortTitle(valB);
  367.                         }
  368.                         else {
  369.                             var valA = a[sorts[index].field];
  370.                             var valB = b[sorts[index].field];
  371.                         }
  372.                         
  373.                         // Put empty values last
  374.                         if (!valA && valB) {
  375.                             var cmp = 1;
  376.                         }
  377.                         else if (valA && !valB) {
  378.                             var cmp = -1;
  379.                         }
  380.                         else {
  381.                             var cmp = collation.compareString(0, valA, valB);
  382.                         }
  383.                         
  384.                         var result = 0;
  385.                         if (cmp != 0) {
  386.                             result = cmp * sorts[index].order;
  387.                         }
  388.                         index++;
  389.                     }
  390.                     while (result == 0 && sorts[index]);
  391.                     
  392.                     return result;
  393.                 };
  394.                 
  395.                 items.sort(compareFunction);
  396.                 for (var i in items) {
  397.                     if (items[i].reportChildren) {
  398.                         items[i].reportChildren.notes.sort(compareFunction);
  399.                         items[i].reportChildren.attachments.sort(compareFunction);
  400.                     }
  401.                 }
  402.                 
  403.                 // Pass off to the appropriate handler
  404.                 switch (format){
  405.                     case 'rtf':
  406.                         mimeType = 'text/rtf';
  407.                         break;
  408.                         
  409.                     case 'csv':
  410.                         mimeType = 'text/plain';
  411.                         break;
  412.                     
  413.                     default:
  414.                         format = 'html';
  415.                         mimeType = 'application/xhtml+xml';
  416.                         content = Zotero.Report.generateHTMLDetails(items, combineChildItems);
  417.                 }
  418.             }
  419.             catch (e){
  420.                 Zotero.debug(e);
  421.                 throw (e);
  422.             }
  423.             
  424.             var uri_str = 'data:' + (mimeType ? mimeType + ',' : '') + encodeURIComponent(content);
  425.             var ext_uri = ioService.newURI(uri_str, null, null);
  426.             var extChannel = ioService.newChannelFromURI(ext_uri);
  427.             
  428.             return extChannel;
  429.         }
  430.     };
  431.  
  432.     var TimelineExtension = new function(){
  433.         this.newChannel = newChannel;
  434.         
  435.         this.__defineGetter__('loadAsChrome', function () { return true; });
  436.         
  437.         /*
  438.         queryString key abbreviations:  intervals = i  |  dateType = t  |  timelineDate = d
  439.         
  440.         interval abbreviations:  day = d  |  month = m  |  year = y  |  decade = e  |  century = c  |  millennium = i
  441.         dateType abbreviations:  date = d  |  dateAdded = da  |  dateModified = dm
  442.         timelineDate format:  shortMonthName.day.year  (year is positive for A.D. and negative for B.C.)
  443.         
  444.         
  445.         
  446.         zotero://timeline   -----> creates HTML for timeline 
  447.             (defaults:  type = library | intervals = month, year, decade | timelineDate = today's date | dateType = date)
  448.             
  449.             
  450.         Example URLs:
  451.         
  452.         zotero://timeline/library?i=yec
  453.         zotero://timeline/collection/12345?t=da&d=Jul.24.2008
  454.         zotero://timeline/search/54321?d=Dec.1.-500&i=dmy&t=d
  455.         
  456.         
  457.         
  458.         zotero://timeline/data    ----->creates XML file
  459.             (defaults:  type = library  |  dateType = date)
  460.         
  461.         
  462.         Example URLs:
  463.         
  464.         zotero://timeline/data/library?t=da
  465.         zotero://timeline/data/collection/12345
  466.         zotero://timeline/data/search/54321?t=dm
  467.         
  468.         */
  469.         function newChannel(uri) {
  470.             var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  471.                 .getService(Components.interfaces.nsIIOService);
  472.  
  473.             var Zotero = Components.classes["@zotero.org/Zotero;1"]
  474.                 .getService(Components.interfaces.nsISupports)
  475.                 .wrappedJSObject;
  476.  
  477.             generateContent:try {
  478.                 var mimeType, content = '';
  479.     
  480.                 var [path, queryString] = uri.path.substr(1).split('?');
  481.                 var [intervals, timelineDate, dateType] = ['','',''];
  482.                 
  483.                 if (queryString) {
  484.                     var queryVars = queryString.split('&');
  485.                     for (var i in queryVars) {
  486.                         var [key, val] = queryVars[i].split('=');
  487.                         if(val) {
  488.                             switch (key) {
  489.                                 case 'i':
  490.                                     intervals = val;
  491.                                     break;
  492.                                 case 'd':
  493.                                     timelineDate = val;
  494.                                     break;
  495.                                 case 't':
  496.                                     dateType = val;
  497.                                     break;
  498.                             }
  499.                         }
  500.                     }
  501.                 }
  502.                 
  503.                 var pathParts = path.split('/');
  504.                 
  505.                 if (pathParts[0] != 'data') {
  506.                     //creates HTML file
  507.                     content = Zotero.File.getContentsFromURL('chrome://zotero/skin/timeline/timeline.html');
  508.                     mimeType = 'text/html';
  509.                     
  510.                     var [type, id] = pathParts;
  511.                                         
  512.                     if(!timelineDate){
  513.                         timelineDate=Date();
  514.                         var dateParts=timelineDate.toString().split(' ');
  515.                         timelineDate=dateParts[1]+'.'+dateParts[2]+'.'+dateParts[3];
  516.                     }
  517.                     if (intervals.length < 3) {
  518.                         intervals += "mye".substr(intervals.length);
  519.                     }
  520.                     
  521.                     var theIntervals = new Object();
  522.                         theIntervals['d'] = 'Timeline.DateTime.DAY';
  523.                         theIntervals['m'] = 'Timeline.DateTime.MONTH';
  524.                         theIntervals['y'] = 'Timeline.DateTime.YEAR';
  525.                         theIntervals['e'] = 'Timeline.DateTime.DECADE';
  526.                         theIntervals['c'] = 'Timeline.DateTime.CENTURY';
  527.                         theIntervals['i'] = 'Timeline.DateTime.MILLENNIUM';
  528.                     
  529.                     //sets the intervals of the timeline bands
  530.                     var theTemp = '<body onload="onLoad(';
  531.                     var a = (theIntervals[intervals[0]]) ? theIntervals[intervals[0]] : 'Timeline.DateTime.MONTH';
  532.                     var b = (theIntervals[intervals[1]]) ? theIntervals[intervals[1]] : 'Timeline.DateTime.YEAR';
  533.                     var c = (theIntervals[intervals[2]]) ? theIntervals[intervals[2]] : 'Timeline.DateTime.DECADE';
  534.                     content = content.replace(theTemp, theTemp + a + ',' + b + ',' + c + ',\'' + timelineDate + '\'');
  535.                     
  536.                     theTemp = 'document.write("<title>';
  537.                     if(type == 'collection') {
  538.                         var theCollection = Zotero.Collections.get(id);
  539.                         content = content.replace(theTemp, theTemp + theCollection.getName() + ' - ');
  540.                     }
  541.                     else if(type == 'search') {
  542.                         var theSearch = Zotero.Searches.get(id);
  543.                         content = content.replace(theTemp, theTemp + theSearch['name'] + ' - ');
  544.                     }
  545.                     else {
  546.                         content = content.replace(theTemp, theTemp + Zotero.getString('pane.collections.library') + ' - ');
  547.                     }
  548.                     
  549.                     theTemp = 'Timeline.loadXML("zotero://timeline/data/';
  550.                     var d = '';
  551.                     //passes information (type,ids, dateType) for when the XML is created
  552.                     if(!type || (type != 'collection' && type != 'search')) {
  553.                         d += 'library?t=' + dateType;
  554.                     }
  555.                     else {
  556.                         d += type + '/' + id + '?t=' + dateType;
  557.                     }
  558.                     content = content.replace(theTemp, theTemp + d);
  559.                     
  560.                     
  561.                     var uri_str = 'data:' + (mimeType ? mimeType + ',' : '') + encodeURIComponent(content);
  562.                     var ext_uri = ioService.newURI(uri_str, null, null);
  563.                     var extChannel = ioService.newChannelFromURI(ext_uri);
  564.  
  565.                     return extChannel;
  566.                 }
  567.                 else {
  568.                     //creates XML file
  569.                     var [, type, ids] = pathParts;
  570.                     
  571.                     switch (type){
  572.                         case 'collection':
  573.                             var col = Zotero.Collections.get(ids);
  574.                             var results = col.getChildItems();
  575.                             break;
  576.  
  577.                         case 'search':
  578.                             var s = new Zotero.Search(ids);
  579.                             var ids = s.search();
  580.                             break;
  581.  
  582.                         default:
  583.                             type = 'library';
  584.                             var s = new Zotero.Search();
  585.                             s.addCondition('noChildren', 'true');
  586.                             var ids = s.search();
  587.                     }
  588.  
  589.                     if (!results) {
  590.                         var results = Zotero.Items.get(ids);
  591.                     }
  592.                     var items = [];
  593.                     // Only include parent items
  594.                     for (var i = 0; i < results.length; i++) {
  595.                         if (!results[i].getSource()) {
  596.                             items.push(results[i]);
  597.                         }
  598.                     }
  599.  
  600.                     if (!items) {
  601.                         mimeType = 'text/html';
  602.                         content = 'Invalid ID';
  603.                         break generateContent;
  604.                     }
  605.  
  606.                     // Convert item objects to export arrays
  607.                     for (var i = 0; i < items.length; i++) {
  608.                         items[i] = items[i].toArray();
  609.                     }
  610.  
  611.                     mimeType = 'application/xml';
  612.  
  613.                 
  614.                     var theDateTypes = new Object();
  615.                         theDateTypes['d'] = 'date';
  616.                         theDateTypes['da'] = 'dateAdded';
  617.                         theDateTypes['dm'] = 'dateModified';
  618.                     
  619.                     //default dateType = date
  620.                     if (!dateType || !theDateTypes[dateType]) {
  621.                         dateType = 'd';
  622.                     }
  623.                 
  624.                     content = Zotero.Timeline.generateXMLDetails(items, theDateTypes[dateType]);
  625.  
  626.                 }
  627.                 
  628.                 var uri_str = 'data:' + (mimeType ? mimeType + ',' : '') + encodeURIComponent(content);
  629.                 var ext_uri = ioService.newURI(uri_str, null, null);
  630.                 var extChannel = ioService.newChannelFromURI(ext_uri);
  631.  
  632.                 return extChannel;
  633.             }
  634.             catch (e){
  635.                 Zotero.debug(e);
  636.                 throw (e);
  637.             }
  638.         }
  639.     };
  640.     
  641.     
  642.     /*
  643.         zotero://attachment/[id]/
  644.     */
  645.     var AttachmentExtension = new function() {
  646.         this.newChannel = newChannel;
  647.         
  648.         this.__defineGetter__('loadAsChrome', function () { return false; });
  649.         
  650.         function newChannel(uri) {
  651.             var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  652.                 .getService(Components.interfaces.nsIIOService);
  653.             
  654.             var Zotero = Components.classes["@zotero.org/Zotero;1"]
  655.                 .getService(Components.interfaces.nsISupports)
  656.                 .wrappedJSObject;
  657.             
  658.             try {
  659.                 var errorMsg;
  660.                 var [id, fileName] = uri.path.substr(1).split('/');
  661.                 
  662.                 if (parseInt(id) != id) {
  663.                     // Proxy annotation icons
  664.                     if (id.match(/^annotation.*\.(png|html|css|gif)$/)) {
  665.                         var chromeURL = 'chrome://zotero/skin/' + id;
  666.                         var ios = Components.classes["@mozilla.org/network/io-service;1"].
  667.                                     getService(Components.interfaces.nsIIOService);
  668.                         var uri = ios.newURI(chromeURL, null, null);
  669.                         var chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  670.                                 .getService(Components.interfaces.nsIChromeRegistry);
  671.                         var fileURI = chromeReg.convertChromeURL(uri);
  672.                     }
  673.                     else {
  674.                         return _errorChannel("Attachment id not an integer");
  675.                     }
  676.                 }
  677.                 
  678.                 if (!fileURI) {
  679.                     var item = Zotero.Items.get(id);
  680.                     if (!item) {
  681.                         return _errorChannel("Item not found");
  682.                     }
  683.                     var file = item.getFile();
  684.                     if (!file) {
  685.                         return _errorChannel("File not found");
  686.                     }
  687.                     if (fileName) {
  688.                         file = file.parent;
  689.                         file.append(fileName);
  690.                         if (!file.exists()) {
  691.                             return _errorChannel("File not found");
  692.                         }
  693.                     }
  694.                 }
  695.                 
  696.                 var ph = Components.classes["@mozilla.org/network/protocol;1?name=file"].
  697.                         createInstance(Components.interfaces.nsIFileProtocolHandler);
  698.                 if (!fileURI) {
  699.                     var fileURI = ph.newFileURI(file);
  700.                 }
  701.                 var channel = ioService.newChannelFromURI(fileURI);
  702.                 return channel;
  703.             }
  704.             catch (e) {
  705.                 Zotero.debug(e);
  706.                 throw (e);
  707.             }
  708.         }
  709.         
  710.         
  711.         function _errorChannel(msg) {
  712.             var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  713.                 .getService(Components.interfaces.nsIIOService);
  714.             var uriStr = 'data:text/plain,' + encodeURIComponent(msg);
  715.             var dataURI = ioService.newURI(uriStr, null, null);
  716.             var channel = ioService.newChannelFromURI(dataURI);
  717.             return channel;
  718.         }
  719.     };
  720.     
  721.     
  722.     /*
  723.         zotero://select/type/id
  724.     */
  725.     var SelectExtension = new function(){
  726.         this.newChannel = newChannel;
  727.         
  728.         function newChannel(uri) {
  729.             var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  730.                 .getService(Components.interfaces.nsIIOService);
  731.  
  732.             var Zotero = Components.classes["@zotero.org/Zotero;1"]
  733.                 .getService(Components.interfaces.nsISupports)
  734.                 .wrappedJSObject;
  735.  
  736.             generateContent:try {
  737.                 var mimeType, content = '';
  738.     
  739.                 var [path, queryString] = uri.path.substr(1).split('?');
  740.                 var [type, id] = path.split('/');
  741.                 
  742.                 //currently only able to select one item
  743.                 var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  744.                     .getService(Components.interfaces.nsIWindowMediator);
  745.                 var win = wm.getMostRecentWindow(null);
  746.                 
  747.                 if(!win.ZoteroPane.isShowing()){
  748.                     win.ZoteroPane.toggleDisplay();
  749.                 }
  750.                 
  751.                 win.ZoteroPane.selectItem(id);
  752.             }
  753.             catch (e){
  754.                 Zotero.debug(e);
  755.                 throw (e);
  756.             }
  757.         }
  758.     };
  759.  
  760.     
  761.     var ReportExtensionSpec = ZOTERO_SCHEME + "://report"
  762.     this._extensions[ReportExtensionSpec] = ReportExtension;
  763.  
  764.     var TimelineExtensionSpec = ZOTERO_SCHEME + "://timeline"
  765.     this._extensions[TimelineExtensionSpec] = TimelineExtension;
  766.     
  767.     var AttachmentExtensionSpec = ZOTERO_SCHEME + "://attachment"
  768.     this._extensions[AttachmentExtensionSpec] = AttachmentExtension;
  769.     
  770.     var SelectExtensionSpec = ZOTERO_SCHEME + "://select"
  771.     this._extensions[SelectExtensionSpec] = SelectExtension;
  772. }
  773.  
  774.  
  775. /*
  776.  * Implements nsIProtocolHandler
  777.  */
  778. ChromeExtensionHandler.prototype = {
  779.     scheme: ZOTERO_SCHEME,
  780.     
  781.     defaultPort : -1,
  782.     
  783.     protocolFlags :
  784.         Components.interfaces.nsIProtocolHandler.URI_NORELATIVE |
  785.         Components.interfaces.nsIProtocolHandler.URI_NOAUTH |
  786.         // DEBUG: This should be URI_IS_LOCAL_FILE, and MUST be if any
  787.         // extensions that modify data are added
  788.         //  - https://www.zotero.org/trac/ticket/1156
  789.         //
  790.         //Components.interfaces.nsIProtocolHandler.URI_IS_LOCAL_FILE,
  791.         Components.interfaces.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE,
  792.         
  793.     allowPort : function(port, scheme) {
  794.         return false;
  795.     },
  796.     
  797.     newURI : function(spec, charset, baseURI) {
  798.         var newURL = Components.classes["@mozilla.org/network/standard-url;1"]
  799.             .createInstance(Components.interfaces.nsIStandardURL);
  800.         newURL.init(1, -1, spec, charset, baseURI);
  801.         return newURL.QueryInterface(Components.interfaces.nsIURI);
  802.     },
  803.     
  804.     newChannel : function(uri) {
  805.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  806.             .getService(Components.interfaces.nsIIOService);
  807.         
  808.         var chromeService = Components.classes["@mozilla.org/network/protocol;1?name=chrome"]
  809.             .getService(Components.interfaces.nsIProtocolHandler);
  810.         
  811.         var newChannel = null;
  812.         
  813.         try {
  814.             var uriString = uri.spec.toLowerCase();
  815.             
  816.             for (var extSpec in this._extensions) {
  817.                 var ext = this._extensions[extSpec];
  818.                 
  819.                 if (uriString.indexOf(extSpec) == 0) {
  820.                     if (ext.loadAsChrome && this._systemPrincipal == null) {
  821.                         var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null);
  822.                         var chromeChannel = chromeService.newChannel(chromeURI);
  823.                         
  824.                         // Cache System Principal from chrome request
  825.                         // so proxied pages load with chrome privileges
  826.                         this._systemPrincipal = chromeChannel.owner;
  827.                         
  828.                         var chromeRequest = chromeChannel.QueryInterface(Components.interfaces.nsIRequest);
  829.                         chromeRequest.cancel(0x804b0002); // BINDING_ABORTED
  830.                     }
  831.                     
  832.                     var extChannel = ext.newChannel(uri);
  833.                     // Extension returned null, so cancel request
  834.                     if (!extChannel) {
  835.                         var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null);
  836.                         var extChannel = chromeService.newChannel(chromeURI);
  837.                         var chromeRequest = extChannel.QueryInterface(Components.interfaces.nsIRequest);
  838.                         chromeRequest.cancel(0x804b0002); // BINDING_ABORTED
  839.                     }
  840.                     
  841.                     // Apply cached system principal to extension channel
  842.                     if (ext.loadAsChrome) {
  843.                         extChannel.owner = this._systemPrincipal;
  844.                     }
  845.                     
  846.                     extChannel.originalURI = uri;
  847.                     
  848.                     return extChannel;
  849.                 }
  850.             }
  851.             
  852.             // pass request through to ChromeProtocolHandler::newChannel
  853.             if (uriString.indexOf("chrome") != 0) {
  854.                 uriString = uri.spec;
  855.                 uriString = "chrome" + uriString.substring(uriString.indexOf(":"));
  856.                 uri = chromeService.newURI(uriString, null, null);
  857.             }
  858.             
  859.             newChannel = chromeService.newChannel(uri);
  860.         }
  861.         catch (e) {
  862.             throw Components.results.NS_ERROR_FAILURE;
  863.         }
  864.         
  865.         return newChannel;
  866.     },
  867.     
  868.     QueryInterface : function(iid) {
  869.         if (!iid.equals(Components.interfaces.nsIProtocolHandler) &&
  870.                 !iid.equals(Components.interfaces.nsISupports)) {
  871.             throw Components.results.NS_ERROR_NO_INTERFACE;
  872.         }
  873.         return this;
  874.     }
  875. };
  876.  
  877.  
  878. //
  879. // XPCOM goop
  880. //
  881.  
  882. var ChromeExtensionModule = {
  883.     cid: ZOTERO_PROTOCOL_CID,
  884.     
  885.     contractId: ZOTERO_PROTOCOL_CONTRACTID,
  886.     
  887.     registerSelf : function(compMgr, fileSpec, location, type) {
  888.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  889.         compMgr.registerFactoryLocation(
  890.             ZOTERO_PROTOCOL_CID, 
  891.             ZOTERO_PROTOCOL_NAME, 
  892.             ZOTERO_PROTOCOL_CONTRACTID, 
  893.             fileSpec, 
  894.             location,
  895.             type
  896.         );
  897.     },
  898.     
  899.     getClassObject : function(compMgr, cid, iid) {
  900.         if (!cid.equals(ZOTERO_PROTOCOL_CID)) {
  901.             throw Components.results.NS_ERROR_NO_INTERFACE;
  902.         }
  903.         if (!iid.equals(Components.interfaces.nsIFactory)) {
  904.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  905.         }
  906.         return this.myFactory;
  907.     },
  908.     
  909.     canUnload : function(compMgr) {
  910.         return true;
  911.     },
  912.     
  913.     myFactory : {
  914.         createInstance : function(outer, iid) {
  915.             if (outer != null) {
  916.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  917.             }
  918.             
  919.             return new ChromeExtensionHandler().QueryInterface(iid);
  920.         }
  921.     }
  922. };
  923.  
  924. function NSGetModule(compMgr, fileSpec) {
  925.     return ChromeExtensionModule;
  926. }
  927.